﻿#====================================
# RePoser - Pose Library
# ToolKit - zefToolKit
# version: .1
# author:  Matt Murray
#====================================

import maya.cmds as cmds
import os
from os.path import join
from functools import partial
import xml.dom.minidom as xd
from xml.dom.minidom import Document

class RePoser():
    
    def __init__(self):
        print("constructor")
        #Globals
        print(cmds.workspace(q=True, fn=True))
        self.baseURL = cmds.workspace(q=True, fn=True) #get the baseURL
        self.charDir = self.baseURL + '/characters'  #get base char Dir
        self.currentCharDir = ''
        self.charFile = '' #the file where the current character info will be stored
        self.mostRecent = ''
        self.mostRecentName = ''
        self.noRecent = True
        self.selectedChar = ''
        self.tabList = []
        self.selectionSetList = []
        self.poseName = ''
        self.poseSelectionCheck = 'all'
        self.ver = 'v0.1'
        
        #UI Element dict
        self.UIElements = {}
        
        #defaults
        self.thumbWH = [64,64]   #default thumbnail dimensions
        
        #create a default folder so there is at least one there all the time
        if not os.path.exists(self.charDir):
            newMessage = cmds.confirmDialog(title='Newbie', message="I see this is the first time you are using this thing!\n Just a heads up, the current dir being used is your currently set\n project folder. You can change that after the fact. Check out the help button to learn how to use it.", button=['OK'], defaultButton='OK');
            os.mkdir(self.charDir)
        
        #find the most recent character if any
        if not os.path.exists(self.charDir+'/default'):
            os.mkdir(self.charDir+'/default')
            os.mkdir(self.charDir+'/default/poses')
            os.mkdir(self.charDir+'/default/poses/thumbs')
        #check to see if the recent.repo file exists and if it does, read the path of the most recent char and set that as the dir
        if os.path.exists(self.charDir+'/repo.recent'):
            recent = xd.parse(self.charDir+'/repo.recent')
            rootNode = recent.firstChild
            rec = rootNode.getElementsByTagName("recent")
            file = rec[0].attributes['file']
            name = rec[0].attributes['name']
            self.mostRecent = file.value
            self.mostRecentName = name.value
           
            self.noRecent = False
                
        self.start()
        
    def start(self):   #===========START RE-POSER================
        """Create the first window with options to start RePoser"""
        
        #create a window
        self.startWindow = cmds.window(title='RePoser Start', width=200, height=250, s=False, rtf=True)
        cmds.rowColumnLayout(columnAlign=[1,'center'])
        cmds.text(l='Select an Option Below')
        cmds.text(l='')
        cmds.setParent('..')
        self.startLayout = cmds.rowColumnLayout(cw = [(1, 166),], columnAlign=[(1, 'center')])
        
        cmds.text(label='Load Most Recent Character?')
        cmds.text(l='')
        self.lastBtn = cmds.button(label='OK', c=self.checkRecent)
        cmds.separator()
        cmds.text(label='Load Existing Character?')
        cmds.text(l='')
        self.exisitingBtn = cmds.button(label='LOAD', c=self.loadExisting)
        cmds.separator()
        cmds.text(label='Start a New Character')
        cmds.text(l='')
        self.newBtn = cmds.button(label='START', c=self.newChar)
        
        self.existListWin = ''
                
        cmds.showWindow(self.startWindow)
        #self.buildUI()
        
    def checkRecent(self, *args):
        """Find the most recent character if there is one"""
        #check and see if there is a recent file there or not
        #print(self.noRecent)
        if self.noRecent:
            self.recentWarn = cmds.confirmDialog(title = 'Warning!', message='No Recent Characters Found. Please Create a New Character or Load One.', button=['OK'], defaultButton='OK')
            return
        else:
            #get name from file and build UI
            self.selectedChar = self.mostRecentName
            self.currentCharDir = self.mostRecent
            #print(self.currentCharDir +'/'+ self.mostRecentName)
            self.buildUI(True)
        
    
    def newChar(self, *args):   #CREATE A NEW CHARACTER=====================
        """Get the name of the new character from the user"""
        #delete the start window
        cmds.deleteUI(self.startWindow)
        
        #make a new window with a text input and a button
        self.newCharWin = cmds.window(title="New Character Creation", width=200, height=100, rtf=True, s=False)
        self.newCharLayout = cmds.rowColumnLayout( cw =[1, 200], columnAlign=[1,'center'])
        
        cmds.text(l='Character Name')
        self.newCharName = cmds.textField()
        
        cmds.text(l='')
        cmds.text(l='Select a Directory to Save to.')
        self.newCharBtn = cmds.button(l='SELECT', c=self.setDir)   #call the setDir method on click
        
        cmds.showWindow(self.newCharWin)
        
    def setDir(self, *args):    #SET CHARACTER DIR=====================
        """Get the directory location for the character from the user"""
        #make sure there is text in newChar win
        if cmds.textField(self.newCharName, q=True, text=True) == "":
            cmds.confirmDialog(m="You need to enter a name!", button=["OK"], db="OK", icn='warning', ma='center', dismissString="OK")
        else:
            #open a dialog box to pick a DIR
            dialog = cmds.fileDialog2(ds=1, dir=self.charDir, cap='Set Character DIR', fm=3)
            
            #create folder for character
            self.selectedChar = cmds.textField(self.newCharName, q=True, text=True)
            self.charDir = (''+ dialog[0] + '/' + self.selectedChar)
            os.makedirs(self.charDir)
            
            #create a default pose tab/folder
            os.makedirs(self.charDir+'/Poses')
            os.makedirs(self.charDir+'/Poses/thumbs')
            
            self.currentCharDir = self.charDir  #set current charDir as char dir
            self.charDir = self.baseURL + '/characters' #set char dir back
            self.buildUI()
        
        
    def loadExisting(self, *args):
        """Make a list of all the character folders to select a character to load."""
        self.charDir = self.baseURL + '/characters'
        
        #print(self.charDir)
        
        self.existListWin = cmds.window(title='Character List', w=100, h=150, sizeable=False)
        self.listLayout = cmds.rowColumnLayout()
        
        cmds.text(l='Select a Character')
        
        self.existList = cmds.textScrollList()
        
        paths = self.listFolders(self.charDir)
        
        for d in paths:
            cmds.textScrollList(self.existList, edit=True, append=d, sc=self.setExistSel)
            
        cmds.separator()
        cmds.setParent('..')
        cmds.rowColumnLayout(nc=2)
        self.changeDirBtn = cmds.button(l="CHANGE DIR", c=self.changeDir)    
        self.existListBtn = cmds.button(l="SELECT" , c=self.buildUI)
        
        cmds.showWindow(self.existListWin)
        
        #close the window and build the UI
        cmds.deleteUI(self.startWindow)
        
    def changeDir(self, *args):
        """Prompt the user to select a new directory location."""
        #open a dialog box to pick a DIR
        self.newCharDir = cmds.fileDialog2(ds=1, dir=self.charDir, cap='Set Character DIR', fm=3)
        
        if self.newCharDir > 0:
            #clear list
            cmds.textScrollList(self.existList, edit=True, ra=True)
            
            paths = self.listFolders(self.newCharDir[0])
            #add back to list
            for d in paths:
                cmds.textScrollList(self.existList, edit=True, append=d, sc=self.setExistSel)
            self.charDir = self.newCharDir[0]
            self.selectedChar = ''
        
    def setExistSel(self, *args):
        """Once a character has been selected, set the current character directory"""
        #set the charDir to match the selected char
        charName = cmds.textScrollList(self.existList, q=True, selectItem=True)
        self.selectedChar = charName[0]
        self.currentCharDir = self.charDir + '/' + self.selectedChar
    
    def makeCharFile(self, dir):
        """Create the folder and dir for the character to be created"""
        #create a new file in the selected dir 
        if not os.path.exists(self.currentCharDir + '/' + self.selectedChar + '.repo'):
            
            xFile = Document()
            rootNode = xFile.createElement('character')
            xFile.appendChild(rootNode)
            
            cDir = xFile.createElement('dir')
            rootNode.appendChild(cDir)
            cDir.setAttribute('dir', self.currentCharDir)
            
            f = open(self.currentCharDir + '/' + self.selectedChar + '.repo', 'w' )
            f.write(xFile.toprettyxml())
            f.close()
            
        #set the character file
        self.charFile = self.currentCharDir +'/'+ self.selectedChar +'.repo'
        
        
    def buildUI(self, recent=False,*args):
        """Build the main RePoser User Interface"""
        #print("building the UI")
        
        if self.currentCharDir == '':
            cmds.confirmDialog(t="Warning", m="There are no valid characters in this directory, please pick another.", button=['OK'], defaultButton='OK')
            return
        
        self.makeCharFile(self.charDir)
        
        if cmds.window(self.startWindow, exists=True):  #close startWin
            cmds.deleteUI(self.startWindow)
        if cmds.window(self.existListWin, exists=True):  #close listWin
            cmds.deleteUI(self.existListWin)
        
        #load the most recent file
        if recent:
            self.charDir = self.mostRecent
            self.currentCharDir = self.mostRecent
        else:
            #write the recent file to refelect the current selection
            recentDoc = Document()
            rootNode = recentDoc.createElement("poser")
            recentDoc.appendChild(rootNode)
            fileName = recentDoc.createElement("recent")
            rootNode.appendChild(fileName)
            fileName.setAttribute('file', self.charDir +'/'+self.selectedChar)
            fileName.setAttribute('name', self.selectedChar)
            
            f = open(self.charDir+'/repo.recent', 'w')
            f.write(recentDoc.toprettyxml())
            f.close()
        
        #check the char file to see if there are character ctrls store in it or not, if so then store in list
        if len(self.selectionSetList) < 1:
            self.getCharCtrls()
            
        #print(self.charDir + "\n" + self.selectedChar + "\n")
        
        #cmds.deleteUI(self.existListWin)
        
        winWidth = 682
        winHeight = 400
        self.mainWin = cmds.window(title='RePoser '+ self.ver +' - Current Character: ' + self.selectedChar, width=winWidth, height=winHeight, menuBar=True, sizeable=False, mnb=False,mxb=False, rtf=True)
        
        #create menu bar and options=========================
        cmds.menu(label='File')
        cmds.menuItem(label='New Character', c=self.newChar)
        cmds.menuItem(label='Load Character', c=self.loadExisting)
        cmds.menuItem(divider=True)
        cmds.menuItem(label='Exit', c=partial(self.killWin, self.mainWin))
        cmds.menu(label='Options')
        cmds.menuItem(label='Add New Tab', c=self.addSectionWin)
        cmds.menuItem(label='Rename Selected Tab', c=self.renameSectionWin)
        cmds.menuItem(label='Delete Selected Tab', c=self.delTab)
        cmds.menuItem(divider=True)
        cmds.menuItem(label='Add Selection Set', c=self.getCharCtrlsWin)
        cmds.menuItem(label='Remove Selection Set', c=self.removeSelSetWin)
        cmds.menuItem(divider=True)
        cmds.menuItem(subMenu=True, label='Thumb Size')
        cmds.menuItem(label='64x64', c=partial(self.setThumbSize, [64,64]))
        cmds.menuItem(label='128x128', c=partial(self.setThumbSize, [128,128]))
        cmds.menuItem(label='256x256', c=partial(self.setThumbSize, [256,256]))
        cmds.setParent('..', menu=True)
        
        #main layout of the ui
        self.mainLayout = cmds.rowColumnLayout()
        
        self.topFrame = cmds.frameLayout(l='Pose Library', bs='etchedOut', w=winWidth, h=winHeight)
        self.mainColumns = cmds.rowColumnLayout(nc=2, columnWidth=[(1,522),(2,150)], columnAlign=[(1,'left'),(2, 'left')], rat=[(1, 'top', 1), (2, 'both', 1)])

        #===================TABS=======================
        self.tabList = self.listFolders(self.currentCharDir) #get list of folders in the character dir 
        
        #take out any deleted folders
        for f in self.tabList:
            if '_delete' in f:
                self.tabList.pop(self.tabList.index(f))
                
        self.mainTabs = cmds.tabLayout()
        self.tabs = []
        self.scrollThumbs = []
        self.thumbs = []
        i=0
        if self.tabList != 0:
            #build tabs based on subFolders
            for dir in self.tabList:
                    
                self.tabs.append(cmds.shelfLayout(dir, cwh=self.thumbWH, h=winHeight - 45)) #add a scroll layout per tab
                #find thumbs and add to scrollLayout
                self.thumbList = []
                #make sure list is only .png
                for t in os.listdir(self.currentCharDir + '/'+ dir +'/thumbs'):
                    if t.startswith('.'):
                        print('SKIPPING---' + t + '-------------')
                    else:
                        #make sure the file hasn't been marked as deleted by the user
                        if not '_delete' in t:
                            self.thumbList.append(t)
                
                self.thumbs = []
                for t in self.thumbList:
                    #get the pose name by taking the .png off the end of the name
                    poseName = t.split('.')
                    poseName = poseName[0]
                    newThumb = cmds.shelfButton(i=self.currentCharDir+'/'+dir+'/thumbs/'+t, al="center", iol=poseName, ann=poseName, olc=(.9,.9,.9), olb=(0,0,0, .55)) #make the button
                    fpn = cmds.shelfButton(newThumb, q=True, fpn=True)                             #get the full path name
                    cmds.shelfButton(newThumb, edit=True, c=partial(self.setPose, fpn))            #Set the command and pass the fpn
                    self.thumbs.append(newThumb)
                    #print(cmds.shelfButton(newThumb, q=True, pma=True))
                    #make a pop up mennu that is attached to the button
                    #defPopUp = cmds.shelfButton(newThumb, q=True, pma=True)
                    #dpuFpn = cmds.popupMenu(defPopUp[0], e=True, alt=True)
                    #cmds.deleteUI(dpuFpn)
                    cmds.popupMenu(parent=newThumb, ctl=False, button=2)
                    cmds.menuItem(l='Select Controls', c=partial(self.getPoseCtrls, fpn))
                    cmds.menuItem(divider=True)
                    cmds.menuItem(l='Nudge ++', c=partial(self.nudge, fpn, 0.1))
                    cmds.menuItem(l='Nudge --', c=partial(self.nudge, fpn, -0.1))
                    cmds.menuItem(divider=True)
                    cmds.menuItem(l='Set Key', c=partial(self.setKeys,fpn))
                    cmds.menuItem(divider=True)
                    cmds.menuItem(l='Rename', c=partial(self.renamePoseWin, fpn))
                    cmds.menuItem(divider=True)
                    cmds.menuItem(l='Mirror - X axis', c=partial(self.mirrorPose, fpn, 'X'))
                    cmds.menuItem(l='Mirror - Y axis', c=partial(self.mirrorPose, fpn, 'Y'))
                    cmds.menuItem(l='Mirror - Z axis', c=partial(self.mirrorPose, fpn, 'Z'))
                    cmds.menuItem(divider=True)
                    cmds.menuItem(l='Delete', c=partial(self.delPose, fpn))
                    
                    self.scrollThumbs.append(self.thumbs)
                    
                cmds.setParent('..') #back to tab
                
                i += 1
                #print(dir)
                #print(self.tabs)
				
        #-----------------------------------------------------------
        cmds.setParent('..')
        self.btnFrameLayout = cmds.frameLayout(l='Commands')
        self.optionsLayout = cmds.rowColumnLayout(columnAlign=[(1,'left')])
        self.preFix = cmds.textFieldGrp(l='PreFix: ', cat=[(1,'left',0)], columnWidth=[(1,45),(2,98)])
        cmds.separator()
        cmds.text(l='Apply to:')
        self.selectedCheckBox = cmds.checkBox(l='Selected Ctrls', onc=self.checkSelected, v=1)
        self.charSetBox = cmds.checkBox(l='Character Selection Set', onc=self.checkAll, v=0)
        self.selSetMenu = cmds.optionMenu( label='Sets', changeCommand=self.changeSelSet, en=False )
        #check char file for sets, if there are any then add them to list
        self.getSelSetItems()
        
        cmds.text(l='')
        cmds.separator()
        self.setPoseBtn = cmds.button(l='Create Pose', c=self.storePoseWin, bgc=[0.4,0.7,0.2], ann='Creates a New Pose')  #button to store a pose
        cmds.separator()
        
        cmds.setParent('..')
        self.btnLayout = cmds.rowColumnLayout(nc=2, cw=[(1, 73), (2,73)])
        self.setKeyBtn = cmds.button(l='Set Keys', c=self.setKeys, bgc=[0.4,0.7,0.2] , ann='Keys Selected Objects')
        self.delKeyBtn = cmds.button(l='Delete Keys', c=self.delKeys, bgc=[0.9,0.2,0.2], ann='Deletes Keys on Selected Objects')
        cmds.separator()
        cmds.separator()
        cmds.setParent('..')
        cmds.rowColumnLayout(columnAlign=[(1, "left")])
        self.helpBtn = cmds.button(l="Help!", c=self.getHelp, bgc=[0.1, 0.4, 0.8], w=145)
		
		
        cmds.setParent('..') #back to pane
        #cmds.setParent('..') #back to top frame
	            
        
        cmds.showWindow(self.mainWin)
        
    #==================Tab Functions=============================
        
    def addSectionWin(self, *args):
        """Creates a window to allow the user to enter a name for the new tab being created."""
        #get tab name window
        self.tabNameWin = cmds.window(title='Create New Tab', rtf=True)
        self.tabNameLayout = cmds.rowColumnLayout(columnAlign=[1,'center'])
        cmds.text(l='Enter Name for Tab.')
        self.newTabTxt = cmds.textField()
        self.tabNameBtn = cmds.button(l='OK', c=self.addSection)  #call the add section method
        
        cmds.showWindow(self.tabNameWin)
        
    def addSection(self, *args):
        """Create a new tab for the user to store poses in."""
        #set tab name
        self.newTabName = cmds.textField(self.newTabTxt, q=True, text=True)
        cmds.deleteUI(self.tabNameWin)  #get rid of window
        
        winHeight = 500
        #add a tab
        self.tabs.append(cmds.shelfLayout(p=self.mainTabs, cwh=self.thumbWH, h=winHeight)) #add a scroll layout per tab
        cmds.setParent('..') #back to layout
        cmds.tabLayout(self.mainTabs, edit=True, tabLabel=(self.tabs[len(self.tabs)-1],self.newTabName))

        self.tabList.append(self.newTabName)         #add to the main tablist folder name list
        os.makedirs(self.currentCharDir+'/'+self.newTabName) #make the folder with the tab name
        os.makedirs(self.currentCharDir+'/'+self.newTabName+'/thumbs')  #make the thumbs folder in the new tab dir

    def renameSectionWin(self, *args):
        """Create a window to rename the currently selected tab."""
        #make tab name win
        self.newTabNameWin = cmds.window(title="Rename Tab", width=200, height=100)
        self.newTabNameLayout = cmds.rowColumnLayout( cw =[1, 200], columnAlign=[1,'center'])
        
        cmds.text(l='New Tab Name')
        self.newTabName = cmds.textField()
        
        self.newTabNameBtn = cmds.button(l='Rename', c=self.renameSection)   #call the setDir method on click
        
        cmds.showWindow(self.newTabNameWin)


    def renameSection(self, *args):
        """Rename the currently selected tab."""
        #get name from window
        newName = cmds.textField(self.newTabName, q=True, text=True)

        cmds.deleteUI(self.newTabNameWin)

        #get currently selected tab
        tabInd = cmds.tabLayout(self.mainTabs, q=True, selectTabIndex=True)
        #print(tabInd)
        currentTab = self.tabList[tabInd-1]
        
        #rename the tab
        cmds.tabLayout(self.mainTabs, edit=True, tabLabel=(self.tabs[tabInd-1],newName))
        
        #rename the folder
        oldTabFolder = self.charDir +'/'+ currentTab
        #pull the old tab name out of the main list and put in the new one
        self.tabList[tabInd -1] = newName
        newTabFolder = self.charDir + '/' + newName
        #print(oldTabFolder, newTabFolder)
        os.rename(oldTabFolder, newTabFolder)

       
    def delTab(self,*args):
        """Delete the currently selected tab and all the poses within it"""
        #get rid of tab in UI
        #make sure user really wants to
        delTabConf = cmds.confirmDialog(m='Are you sure you want to delete this tab?\nAll thumbnails and poses in it will be lost.', button=['OK', 'CANCEL'], defaultButton='CANCEL', dismissString='CANCEL')
        
        if cmds.confirmDialog(delTabConf, q=True, dismissString=True) == 'CANCEL':
            return
        else:
            tab = self.tabs[cmds.tabLayout(self.mainTabs, q=True, selectTabIndex=True)-1]
            tabName = self.tabList[cmds.tabLayout(self.mainTabs, q=True, selectTabIndex=True)-1]
            
        
            #get path of tab(dir) and delete
            delPath = self.charDir + '/'+ tabName
            thumbPath = delPath + '/thumbs'
            #del the thumbnails
            for t in os.listdir(thumbPath):
                os.rename(thumbPath + '/' + t, thumbPath +'/'+ t+'_delete.png')
            #del the pose Files
            for p in os.listdir(delPath):
                os.rename(delPath + '/' + p, delPath +'/'+p+'_delete')
            #print(delPath + ' was deleted')
            
            #del the dirs
            # Delete everything reachable from the directory named in 'top'.
            # CAUTION: This is dangerous! For example, if top == '/', it
            # could delete all your disk files.
            top = delPath
            #for root, dirs, files in os.walk(top, topdown=False):
            #    #for name in files:
            #        #os.remove(join(root, name))  #get rid of files from dirs
            #    for name in dirs:
            #        os.rename(join(root, name))   #get rid of dirs once the files are gone
            
            #os.removedirs(delPath)
            os.rename(delPath, delPath+'_delete')
            self.tabList.pop(cmds.tabLayout(self.mainTabs, q=True, selectTabIndex=True)-1)  #pull out of main foldername list
            cmds.deleteUI(tab)
            print(tabName + ' was deleted')
        
        
    #================Thumbnail editing==========================
    
    def delPose(self, btn, *args):
        """Get rid of the selected button and pose, add a _delete to the files and folder associated with the pose."""
        #make sure user really wants to
        delPoseConf = cmds.confirmDialog(m='Are you sure you want to delete this pose?\nThis cannot be undone.', button=['OK', 'CANCEL'], defaultButton='CANCEL', dismissString='CANCEL')
        
        if cmds.confirmDialog(delPoseConf, q=True, dismissString=True) == 'CANCEL':
            return
        else:
            #rename pose file to _delete extention
            self.currentTab = self.tabList[cmds.tabLayout(self.mainTabs, q=True, selectTabIndex=True)-1]
            btnName = cmds.shelfButton(btn, q=True, ann=True)
            poseFilePath = self.currentCharDir + '/' + self.currentTab +'/' + btnName +'.pose'
            poseThumbPath = self.currentCharDir + '/' + self.currentTab +'/thumbs/' +btnName+'.png'
            
            os.rename(poseFilePath, self.currentCharDir + '/' + self.currentTab +'/' + btnName+'_delete.pose')
            os.rename(poseThumbPath, self.currentCharDir + '/' + self.currentTab +'/' + btnName+'_delete.png')
            
            #get rid of btn in UI
            cmds.deleteUI(btn)            
            #remove the dir
            
    def setThumbSize(self, size, *args):
        """Update the display size of the thumbnails."""
        self.thumbWH = size
        #print('thumb size set to ' + str(size))
        #refresh the tabs
        for t in self.tabs:
            cmds.shelfLayout(t,edit=True, cwh=self.thumbWH)
            
    def saveThumbnail(self, path,*args):
        """Render a preview of the active viewport. Save that image and a pose associated with the objects selected."""
        #save a playblast of the thumb
        self.time = cmds.currentTime(q=True)
        cmds.setAttr("defaultRenderGlobals.imageFormat", 32);
        cmds.playblast(st=self.time, et=self.time, f=path, fp=0, percent=100, fmt='image', c='png',wh=[256,256], orn=False, v=False)
        
        #cmds.confirmDialog(t='Save was successful!', button=['OK'], db='OK')
        #rename that playblast cause its gay and adds padding I don't want
        oldName = path+'.'+str(int(self.time))+'.png'
        newName = path + '.png'
        #print(oldName +'----'+newName)
        os.rename(oldName, newName)
         
        #add symbol button for pose to tab
        cmds.setParent(self.tabs[cmds.tabLayout(self.mainTabs, q=True, selectTabIndex=True)-1])
        btn = cmds.shelfButton(i=path+'.png', al="center", olc=(.9,.9,.9), olb=(0,0,0, .55))
        fpn = cmds.shelfButton(btn, q=True, fpn=True)
        cmds.shelfButton(btn, e=True, c=partial(self.setPose, fpn), ann=self.poseName, iol=self.poseName)
        cmds.popupMenu(parent=btn, ctl=False, button=2)
        cmds.menuItem(l='Select Controls', c=partial(self.getPoseCtrls, fpn))
        cmds.menuItem(divider=True)
        cmds.menuItem(l='Nudge ++', c=partial(self.nudge, fpn, 0.1))
        cmds.menuItem(l='Nudge --', c=partial(self.nudge, fpn, -0.1))
        cmds.menuItem(divider=True)
        cmds.menuItem(l='Set Key', c=partial(self.setKeys,fpn))
        cmds.menuItem(divider=True)
        cmds.menuItem(l='Rename', c=partial(self.renamePoseWin, fpn))
        cmds.menuItem(divider=True)
        cmds.menuItem(l='Mirror', c=partial(self.mirrorPose, fpn))
        cmds.menuItem(divider=True)
        cmds.menuItem(l='Delete', c=partial(self.delPose, fpn))
        cmds.setParent('..')
        cmds.setParent('..')
                
    #==============Utils functions=============
    
    def listFolders(path, *args):
        """Return a list of folders in the path that is passed in."""
        #print(args[0])
        return [d for d in os.listdir(args[0]) if os.path.isdir(os.path.join(args[0], d))]
        
    def isNumber(self, s):
        """Test and see if the argument passed in is a number or not. Return a boolean for the result."""
        try:
           float(s)
           return True
        except ValueError:
           return False
       
    def killWin(self, win, *args):
        """Close the main UI window."""
        cmds.deleteUI(win)
        
    #==================CTRLS SELECTION FUNCTIONS===================
        
    def getCharCtrlsWin(self, *args):
        """Prompt the user to select the ctrls for the character they wish to store."""
        self.getCtrlsWin = cmds.window(t='Get Character Controls', wh=[200,200], rtf=True)
        cmds.rowColumnLayout(cal=(1,'center'))
        cmds.text(l='Select all of your characters controls you wish to store in this set, Give the set a name and click OK', w=150, ww=True)
        self.newCharSetTxt = cmds.textField(w=150)
        cmds.button(l='OK', c=self.storeCharCtrls, w=150)
        
        cmds.showWindow(self.getCtrlsWin)
                
    def getCharCtrls(self):
        """Get the ctrls that are stored in the character xml file."""
        #open char file
        xfile = xd.parse(self.charFile)
        root = xfile.getElementsByTagName('Ctrl')
        i=0
        for r in root:
            key = root[i].attributes['name'].value  
            self.selectionSetList.append(key)
            i = i +1
            
    def getPoseCtrls(self, fpn, *args):
        """select the ctrls for the button that is passed in"""
        #find btn clicked and tab its in
        poseName = cmds.shelfButton(fpn, q=True, ann=True) #annotation of btn
        #print(poseName)

        #make the dir base on sel
        tabInd = cmds.tabLayout(self.mainTabs, q=True, selectTabIndex=True)
        
        currentTab = self.tabList[cmds.tabLayout(self.mainTabs, q=True, selectTabIndex=True)-1]
        
        #read the pose file and grab all the ctrls
        poseFilePath = self.currentCharDir +'/'+ currentTab + '/' + poseName +'.pose'
        xfile = xd.parse(poseFilePath)
        elements = xfile.getElementsByTagName('Ctrl')
        i=0
        for e in elements:
            ctrl = e.attributes['ctrlName'].value
            cmds.select(ctrl, add=True)
            i = i +1
            #print(key)
        
    def storeCharCtrls(self, *args):
        """Save the selected ctrls in the character xml file."""
        
        #check to see if the user entered a name or not
        if cmds.textField(self.newCharSetTxt, q=True, text=True) =='':
            cmds.confirmDialog(t="Need a name", m="You need to give the set a name")
            return
        
        #get name and add to options menu
        self.newSelectSetName = cmds.textField(self.newCharSetTxt, q=True, text=True)
        cmds.menuItem(l=self.newSelectSetName, p=self.selSetMenu)
        
        cmds.deleteUI(self.getCtrlsWin)
        sel = cmds.ls(selection=True)
        #open the char file and write the ctls to it, store it in the local list as well
        self.selectionSetList = []  #clear the list
        for s in sel:
            self.selectionSetList.append(s)
        
        #open the char file
        xFile = xd.parse(self.charFile)
        rootNode = xFile.firstChild
        #create charSet element
        newSet = xFile.createElement('charSet')
        rootNode.appendChild(newSet)
        newSet.setAttribute('name', self.newSelectSetName)
        #loop through ctrls and add to charset                
        for c in self.selectionSetList:
            newCtrl = xFile.createElement('Ctrl')
            newSet.appendChild(newCtrl)
            newCtrl.setAttribute('name', c)
                
        f = open(self.charFile, 'w')
        f.write(xFile.toprettyxml())
        f.close()
        
    #================Posing Functions==============
        
    def storePoseWin(self, *args):  
        """Get the name of the pose to be saved from the user."""      
        #check to see if the all ctrls is selected and if it is, check if that list has been set
        if self.poseSelectionCheck == 'all':
            if len(self.selectionSetList) < 1:
                cmds.confirmDialog(t="No Controls Set", message='You have not set the controls for your character yet!\nUse the "options" menu to set your character controls.');
                return
        
        #get Pose name
        self.poseNameWin = cmds.window(title='Create New Pose', rtf=True)
        self.poseNameLayout = cmds.rowColumnLayout(columnAlign=[1,'center'])
        cmds.text(l='Enter Name for Pose.')
        self.newPoseTxt = cmds.textField()
        self.poseNameBtn = cmds.button(l='OK', c=self.storePose)  #call the storePose Method
        
        cmds.showWindow(self.poseNameWin)
		
    def renamePoseWin(self, btn, *args):
        """Get the new name of the pose from the user."""
        #print("rename")
        #create the win to grab the new name, then pass that to the btn and rename the .pose file
        self.renamePoseWin = cmds.window(title='Rename Pose', rtf=True)
        self.renamePoseLayout = cmds.rowColumnLayout(columnAlign=[1,'center'])
        cmds.text(l='Enter New Name for Pose.')
        self.renamePoseTxt = cmds.textField()
        self.renamePoseBtn = cmds.button(l='OK', c=partial(self.renamePose, btn))  #call the storePose Method

        self.oldPoseName = cmds.shelfButton(btn, q=True, ann=True)
        
        cmds.showWindow(self.renamePoseWin)

    def renamePose(self, btn, *args):
        """Rename the selected btn and the files associated with it."""
        #grab text from the window
        newName = cmds.textField(self.renamePoseTxt, q=True, text=True)

        #get the button and rename it
        cmds.shelfButton(btn, e=True, ann=newName)
        cmds.shelfButton(btn, e=True, l=newName)
        cmds.shelfButton(btn, e=True, imageOverlayLabel=newName)

        #rename the actual image and pose files
        self.currentTab = self.tabList[cmds.tabLayout(self.mainTabs, q=True, selectTabIndex=True)-1]
        newPath = self.charDir +'/'+ self.currentTab + '/' + newName+'.pose'
        oldPath = self.charDir +'/'+ self.currentTab + '/' + self.oldPoseName+'.pose'
        #print(newPath + " " + oldPath)
        os.rename(oldPath, newPath)
        
        newImageName = self.charDir +'/'+ self.currentTab + '/thumbs/' + newName+'.png'
        oldImageName = self.charDir +'/'+ self.currentTab + '/thumbs/' + self.oldPoseName +'.png'
        #print(newImageName + " " + oldImageName)
        os.rename(oldImageName, newImageName)

        		
    def mirrorPose(self, fpn, axis, *args):
        """Set the values of the selected pose to the opposite values based on the mirror axis."""
        #print("mirror")
        #grab the ctrls and values from the .pose file and
        poseName = cmds.shelfButton(fpn, q=True, ann=True) #annotation of btn
        #print(poseName)
        
        tabInd = cmds.tabLayout(self.mainTabs, q=True, selectTabIndex=True)
        currentTab = self.tabList[cmds.tabLayout(self.mainTabs, q=True, selectTabIndex=True)-1]
        
        poseFilePath = self.currentCharDir +'/'+ currentTab + '/' + poseName +'.pose'
        
        xFile = xd.parse(poseFilePath)
        elements = xFile.getElementsByTagName('Ctrl')
        
        for e in elements:
            attrs = e.attributes.keys()
            ctrl = e.attributes['ctrlName'].value
            pre = cmds.textFieldGrp(self.preFix, q=True, text=True)
            if not pre == '':
                ctrl = pre + ctrl
            #make sure object exists
            if not cmds.objExists(ctrl):
                cmds.confirmDialog(t="Warning", m="Objects in the pose have not been found.\n Aborting pose.", button=['OK'], defaultButton='OK')
                break
            #print(attrs)
            for a in attrs:
                #print(a)
                if a == 'ctrlName':
                    continue
               
                pair = e.attributes[a]
                attribute = pair.name
                value = pair.value
                if value == 'True':
                    value = 1
                if value == 'False':
                    value = 0
                value = float(value)
                if axis in attribute:
                    print("-----" + attribute)
                value = value * -1
                cmds.setAttr(ctrl +'.'+ attribute, value)
		 
		
    def storePose(self, *args):
        """Create a pose for the current character and store information into a text file."""
        #check to see if a pose with the same name already exists
        self.poseName = cmds.textField(self.newPoseTxt, q=True, text=True)
        
        self.currentTab = self.tabList[cmds.tabLayout(self.mainTabs, q=True, selectTabIndex=True)-1]
        
        self.savePoseConf = 'YES'
        self.thumbSavePath = self.currentCharDir +'/'+ self.currentTab +'/thumbs/'+ self.poseName
        
        if os.path.exists(self.thumbSavePath + '.png'):
            #warn user that the file already exists and if they wish to override it or not
            self.savePoseConf = cmds.confirmDialog(m="A pose with this name already exists. Do you wish to save over it?", icn='question', ma='center', t='Are You Sure?', button=['YES', 'NO'], db='YES', cancelButton='NO', dismissString='NO')
        
        #continue to create the pose if ok to override
        if self.savePoseConf == 'YES':
            #save the thumnail
            self.saveThumbnail(self.thumbSavePath)
            
            #save the actual file================
            sel = []
            if self.poseSelectionCheck == 'selected':
                sel = cmds.ls(sl=True)  #get current selection
            elif self.poseSelectionCheck == 'all':
                sel = self.selectionSetList  #get the ctrls of the rig
            #write the recent file to refelect the current selection
            self.writePoseFile(sel,self.currentCharDir +'/'+ self.currentTab + '/' + self.poseName +'.pose', self.thumbSavePath, self.poseName)
                       
            cmds.deleteUI(self.poseNameWin)
            #refresh the tabs
            for t in self.tabs:
                cmds.shelfLayout(t,edit=True, cwh=self.thumbWH)
            
        #print('made Thumb' +'\n'+ self.thumbSavePath)
        
    def writePoseFile(self, objs, path, thumb, name):
        """Write an xml file that stores all the keyable attributes on a list of objects passed in."""
        doc = Document()
        root = doc.createElement("pose")
        doc.appendChild(root)
        infoNode = doc.createElement("info")
        root.appendChild(infoNode)
        infoNode.setAttribute("thumbnail", thumb + ".png")
        infoNode.setAttribute("name", name)
        
        #loop through each unlocked attr in each selected obj write name and value to file
        #print(objs)
        for o in objs:
            attrs = cmds.listAttr(o, keyable=True, unlocked=True) #get the list of attrs for that ctrl
            objNode = doc.createElement('Ctrl')
            root.appendChild(objNode)
            objNode.setAttribute('ctrlName', str(o))
            for atr in attrs:
                #get value of attr
                value = cmds.getAttr(o + '.' + atr)
                objNode.setAttribute(str(atr), str(value))
        
        f = open(path, 'w' )
        f.write(doc.toprettyxml())
        f.close()
        #print(path + ' _was written')
    
    def setPose(self, fpn, *args):
        """Read a pose file and set the ctrls"""
        #get the name of the button clicked
        poseName = cmds.shelfButton(fpn, q=True, ann=True) #annotation of btn
        #print(poseName)
        
        #print('selected tab' + str(cmds.tabLayout(self.mainTabs, q=True, selectTabIndex=True)))
        tabInd = cmds.tabLayout(self.mainTabs, q=True, selectTabIndex=True)
        #print(self.scrollThumbs[tabInd]) #self.thumbs
        #print(fpn)
        #print(self.scrollThumbs[tabInd-1][0])
        
        currentTab = self.tabList[cmds.tabLayout(self.mainTabs, q=True, selectTabIndex=True)-1]
        print(currentTab)
        #read the pose file
        poseFilePath = self.currentCharDir +'/'+ currentTab + '/' + poseName +'.pose'
        
        xFile = xd.parse(poseFilePath)
        
        elements = xFile.getElementsByTagName('Ctrl')
        for e in elements:
            attrs = e.attributes.keys()
            ctrl = e.attributes['ctrlName'].value
            pre = cmds.textFieldGrp(self.preFix, q=True, text=True)
            if not pre == '':
                ctrl = pre + ctrl
            #make sure object exists
            if not cmds.objExists(ctrl):
                cmds.confirmDialog(t="Warning", m="Objects in the pose have not been found.\n Aborting pose.", button=['OK'], defaultButton='OK')
                break
            #print(attrs)
            for a in attrs:
                #print(a)
                if a == 'ctrlName':
                    continue
               
                pair = e.attributes[a]
                attribute = pair.name
                value = pair.value
                if value == 'True':
                    value = 1
                if value == 'False':
                    value = 0
                cmds.setAttr(ctrl +'.'+ attribute, float(value))
                
                    
            
        #print(poseList)
    
    def override(self, fpn, *args):
        """save over the selected poses file"""
        poseName = cmds.shelfButton(fpn, q=True, ann=True)
        tabName = self.tabList[cmds.tabLayout(self.mainTabs, q=True, selectTabIndex=True)-1]
        thumbName = self.currentCharDir + '/'+tabName+'/thumbs/'+poseName
        poseFile = self.currentCharDir + '/'+tabName +'/'+poseName+'.pose'
        
        ctrls = []
        #get selection, first open the xml and get the info out of it
        xFile = xd.parse(poseFilePath)
        elements = xFile.getElementsByTagName('Ctrl')
        for e in elements:
            attrs = e.attributes.keys()
            for a in attrs:
                ctrl = e.attributes['ctrlName'].value
                ctrls.append(ctrl)
        
        self.writePoseFile(ctrls, poseFile, thumb, poseName)
        #print("pose was overridden")
        
    def nudge(self, fpn, val, *args):
        """nudge the pose into its position + or -"""
        poseName = cmds.shelfButton(fpn, q=True, ann=True)
        tabName = self.tabList[cmds.tabLayout(self.mainTabs, q=True, selectTabIndex=True)-1]
        thumbName = self.currentCharDir + '/'+tabName+'/thumbs/'+poseName
        poseFile = self.currentCharDir + '/'+tabName +'/'+poseName+'.pose'
        #get selection
        
        #load the xml file and grab the values      
        xFile = xd.parse(poseFile)
        
        elements = xFile.getElementsByTagName('Ctrl')
        for e in elements:
            attrs = e.attributes.keys()
            ctrl = e.attributes['ctrlName'].value
            #make sure object exists
            if not cmds.objExists(ctrl):
                cmds.confirmDialog(t="Warning", m="Objects in the pose have not been found.\n Could not set pose.", button=['OK'], defaultButton='OK')
                break
            #print(attrs)
            for a in attrs:
                #print(a)
                if a == 'ctrlName':
                    continue
                
                pair = e.attributes[a]
                attribute = pair.name
                totalValue = pair.value
                
                #fix the attr to be 1 or zero depending on it being stored as a boolean
                #might need to find a better way around this for custom attrs
                #check to see if totalValue is a number or not and have 2 diff setAttrs?
                if totalValue == 'True':
                    totalValue = 1
                if totalValue == 'False':
                    totalValue = 0
                currentVal = cmds.getAttr(ctrl + '.'+attribute)
                #set the percent of the total that is to be added to the current value (step distance)
                step = (float(totalValue)) * val
                #if the number is zero after the calculation, add some value to it.
                if step == 0.0:
                    #see if val is a posotive or negative number
                    step = step + 0.5
                    if val < 0:
                        step = step * -1
                        
                newVal = currentVal + step  #current + (total/perc)
                #if newVal >= totalValue:
                #    newVal = totalValue
                #elif newVal <= currentVal:
                #    newVal = currentVal
                cmds.setAttr(ctrl +'.'+ attribute, float(newVal))
        
        print("nudge:" + str(newVal))
        
            
    #======================SELECTION FUNCTIONS===================================         
            
    def checkAll(self, *args):
        """Set the check all btn and disable the check selected btn."""
        #disable the other checkbox
        cmds.checkBox(self.selectedCheckBox, e=True, v=False)
        self.poseSelectionCheck = 'all'
        #enable the menu options
        cmds.optionMenu(self.selSetMenu, e=True, en=True)
        
    def checkSelected(self, *args):
        """Set the check selected Btn and disable the check all btn."""
        #disable the other check box
        self.poseSelectionCheck = 'selected'
        cmds.checkBox(self.charSetBox, e=True, v=False)
        #disable the menu options
        cmds.optionMenu(self.selSetMenu, e=True, en=False)
        
    def changeSelSet(self, *args):
        """Change which character seletction set to use to create poses with."""
        setName = cmds.optionMenu(self.selSetMenu, q=True, v=True)
        
        #open the character XML file and get the selections element with the setName
        xFile = xd.parse(self.charFile)
        elements = xFile.getElementsByTagName(setName)
        
        #Load those elements into the selectionSetList of objects
        for e in elements:
            self.selectionSetList.append(e.attributes[e].value)
            print(e.attributes[e].value)
    
    def getSelSetItems(self):
        """Look at the char file and load in any selection sets for the that character."""
        xFile = xd.parse(self.charFile)
        elements = xFile.getElementsByTagName('charSet')
        for e in elements:
            cmds.menuItem(l=e.attributes['name'].value, p=self.selSetMenu)
        
        
    def removeSelSetWin(self, *args):
        """Show the user all the characters selection sets that can be deleted"""
        self.remSelWin = cmds.window(title='Character List', w=100, h=150, sizeable=False)
        self.remSelLayout = cmds.rowColumnLayout()
        
        cmds.text(l='Select a Set to Remove')
        
        self.remSelList = cmds.textScrollList()
        
        #open the char file and get all the char sets
        xFile = xd.parse(self.charFile)
        elements = xFile.getElementsByTagName('charSet') 
        
        for e in elements:
            cmds.textScrollList(self.remSelList, edit=True, append=e.attributes['name'].value)
            
        cmds.separator()
        cmds.setParent('..')
        cmds.rowColumnLayout(nc=2)
        self.changeDirBtn = cmds.button(l="REMOVE", c=self.removeSelSet)    
        self.existListBtn = cmds.button(l="CLOSE" , c=partial(self.killWin, self.remSelWin)) #cmds.deleteUI(self.remSelWin)
        
        cmds.showWindow(self.remSelWin)
        
    def removeSelSet(self, *args):
        """Open the char file and remove the selected char set"""
        selectedSet = cmds.textScrollList(self.remSelList, q=True, si=True)  #<---returns a list, not a string!
        selInd = cmds.textScrollList(self.remSelList, q=True, sii=True)  #<----returns a list, not an int!
        #remove set from XML
        xFile = xd.parse(self.charFile)
        rootNode = xFile.firstChild
        
        elements = xFile.getElementsByTagName('charSet')
        for e in elements:
            if e.attributes['name'].value == selectedSet[0]:
                #remove this element and it's children
                rootNode.removeChild(e)
                
        #re-save the charfile
        f = open(self.charFile, 'w')
        f.write(xFile.toprettyxml())
        f.close()
        
        #remove set from window list and from mainUI optionsMenu
        if not cmds.optionMenu(self.selSetMenu, q=True, en=True):
            cmds.optionMenu(self.selSetMenu, e=True, en=True)
            
        cmds.textScrollList(self.remSelList, e=True, ri=selectedSet)
        
        itemList = cmds.optionMenu(self.selSetMenu, q=True, ill=True)
        cmds.deleteUI(itemList[selInd[0]-1])
        
    #====================KEYING CONTROLS=======================================
    

    def setKeys(self, fpn='none', *args):
        """Set keyframes on the current selection or on a list of ctrls that are pre-defined"""
        #key the ctrls for the selected pose or for selected ctrls or all ctrls for character
        uSel = []
        if fpn != 'none':
            #key based on selected ctrl button (get ctrls from btn file and key those...hmmmm)
            self.getPoseCtrls(fpn)
            cmds.setKeyframe()
            #print(fpn)
        else:
            #key based on selection
            if self.poseSelectionCheck == 'selection':
                #key based on current selection
                uSel = cmds.ls(sl=True)
                self.keyList(uSel)
                
            elif self.poseSelectionCheck == 'all':
                #key based on saved ctrls
                self.keyList(self.charCtrls)
        
    def delKeys(self, *args):
        """Delete the keyframes on the currently selected objects"""
        #delete the keys at the current frame
        sel = cmds.ls(sl=True)
        for s in sel:
            cmds.cutKey(s)

    def keyList(self, list):
        """Set keyframes on all the objects passed in by list"""
        cmds.select(clear=True)
        for i in list:
            cmds.select(i, add=True)
        cmds.setKeyframe()
        
    #=====================GET HELP===================================
    
    def getHelp(self, *args):
        """Display a window with help information from an external file"""
        f=open(self.baseURL+'/characters/RePoserDocs.txt', 'r')
        content = f.readlines()
        f.close()
        
        txt =''
        for c in content:
            txt = txt + c + '\n'
        
        self.helpWin = cmds.window(t='RePoser Documentation',w=400, h=400, rtf=True)
        cmds.rowColumnLayout()
        cmds.text(l='Reposer Help')
        cmds.separator()
        cmds.scrollLayout(w=375, h=375)
        cmds.text(l=txt, al='left', w=350, ww=True)
        
        cmds.showWindow(self.helpWin)

